
/* 
 * Copyright (c) 2007, Andrey Shchekin

 * All rights reserved.

 * Redistribution and use in source and binary forms, with or without modification, are permitted provided 
 * that the following conditions are met:
 *    Redistributions of source code must retain the above copyright notice, this list of conditions and
 * the following disclaimer.
 *    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided with the distribution.
 *    Neither the name of the Andrey Shchekin nor the names of other contributors may be used to endorse or 
 * promote products derived from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;

using Microsoft.VisualStudio.Package;

using MSBuild = Microsoft.Build.BuildEngine;

namespace AshMind.ProjectTypes.Common {
    [CLSCompliant(false)]
    public abstract class ExtendedProjectNode : ProjectNode
    {
        private static readonly string[] EmptyBuildActions = new string[0];

        #region ProjectNode Rewrite
        // AndreySh: This code is copied from base ProjectNode. I prefer this approach
        // to changing ProjectNode itself, since this way is more compatible with future MPF 
        // changes and works with MPF ProjectBase.files import.

        /// <summary>Loads file items from the project file into the hierarchy.</summary>
        protected internal override void ProcessFiles()
        {
            List<String> subitemsKeys = new List<String>();
            Dictionary<String, MSBuild.BuildItem> subitems = new Dictionary<String, MSBuild.BuildItem>();

            // Define a set for our build items. The value does not really matter here.
            Dictionary<String, MSBuild.BuildItem> items = new Dictionary<String, MSBuild.BuildItem>();

            // Process Files
            MSBuild.BuildItemGroup projectFiles = this.BuildProject.EvaluatedItems;
            foreach (MSBuild.BuildItem item in projectFiles)
            {
                if (this.FilterItemToBeAddedToHierarchy(item))
                    continue;

                // MSBuilds tasks/targets can create items (such as object files),
                // such items are not part of the project per say, and should not be displayed.
                // so ignore those items.
                if (!this.IsItemTypeFileType(item.Name))
                    continue;
               
                // If the item is already contained do nothing.
                // TODO: possibly report in the error list that the the item is already contained in the project file similar to Language projects.
                if (items.ContainsKey(item.FinalItemSpec.ToUpperInvariant()))
                    continue;

                // Make sure that we do not want to add the item, dependent, or independent twice to the ui hierarchy
                items.Add(item.FinalItemSpec.ToUpperInvariant(), item);

                string dependentOf = item.GetMetadata(ProjectFileConstants.DependentUpon);

                if (!this.CanFileNodesHaveChilds || String.IsNullOrEmpty(dependentOf))
                {
                    AddIndependentFileNode(item);
                }
                else
                {
                    // We will process dependent items later.
                    // Note that we use 2 lists as we want to remove elements from
                    // the collection as we loop through it
                    subitemsKeys.Add(item.FinalItemSpec);
                    subitems.Add(item.FinalItemSpec, item);
                }
            }

            // Now process the dependent items.
            if (this.CanFileNodesHaveChilds)
            {
                ProcessDependentFileNodes(subitemsKeys, subitems);
            }
        }

        #region Helper methods made accessible
        // AndreySh: These methods are copied as-is. They are private in the base class,
        // so it is the only way to access them without changing base.

        /// <summary>
        /// Add an item to the hierarchy based on the item path
        /// </summary>
        /// <param name="item">Item to add</param>
        /// <returns>Added node</returns>
        protected HierarchyNode AddIndependentFileNode(MSBuild.BuildItem item)
        {
            HierarchyNode currentParent = GetItemParentNode(item);
            return AddFileNodeToNode(item, currentParent);
        }

        /// <summary>
        /// Add a file node to the hierarchy
        /// </summary>
        /// <param name="item">msbuild item to add</param>
        /// <param name="parentNode">Parent Node</param>
        /// <returns>Added node</returns>
        private HierarchyNode AddFileNodeToNode(MSBuild.BuildItem item, HierarchyNode parentNode)
        {
            FileNode node = this.CreateFileNode(new ProjectElement(this, item, false));
            parentNode.AddChild(node);
            return node;
        }

        /// <summary>
        /// Get the parent node of an msbuild item
        /// </summary>
        /// <param name="item">msbuild item</param>
        /// <returns>parent node</returns>
        private HierarchyNode GetItemParentNode(MSBuild.BuildItem item)
        {
            HierarchyNode currentParent = this;
            string strPath = item.FinalItemSpec;

            strPath = Path.GetDirectoryName(strPath);
            if (strPath.Length > 0)
            {
                // Use the relative to verify the folders...
                currentParent = this.CreateFolderNodes(strPath);
            }
            return currentParent;
        }

        #endregion

        #endregion

        /// <summary>
        /// Filter items that should not be processed as file items. Example: References and Visible="false" items.
        /// </summary>
        /// <param name="item">Item to be checked.</param>
        /// <returns>
        ///     <c>true</c> to ignore item.
        ///     <c>false</c> to add item to hierarchy.
        /// </returns>
        protected virtual bool FilterItemToBeAddedToHierarchy(MSBuild.BuildItem item)
        {
            // Ignore the item if it is a reference or folder
            if (this.FilterItemTypeToBeAddedToHierarchy(item.Name))
                return true;

            if (!this.ShouldItemBeVisible(item))
                return true;
            
            return item.FinalItemSpec.StartsWith("..");
        }
        
        protected internal override int ShowAllFiles()
        {
            return base.ShowAllFiles();
        }

        /// <summary>Gets the project item Visible metadata as specified by user.</summary>
        /// <param name="item"><see cref="ProjectElement" /> item to analyze.</param>
        /// <returns>TODO: document</returns>
        protected bool? GetItemVisibleMetadata(MSBuild.BuildItem item) 
        {
            const string VisibleMetadataName = "Visible";

            if (!item.HasMetadata(VisibleMetadataName))
                return null;

            return item.GetMetadata(VisibleMetadataName) != "false";
        }

        /// <summary>Determines whether the project item should be visible.</summary>
        /// <param name="item"><see cref="ProjectElement" /> item to analyze.</param>
        /// <returns>TODO: document</returns>
        protected bool ShouldItemBeVisible(MSBuild.BuildItem item) 
        {
            // AndreySh: Ignore the item if it has Visible=false or if Visible is not explicitly 
            // specified and item is imported
            // See http://blogs.msdn.com/msbuild/archive/2005/11/03/484773.aspx
            return this.GetItemVisibleMetadata(item) ?? !(item.IsImported);
        }

        /// <summary>Gets a list of all default build actions for the project.</summary>
        /// <returns>Array of <see cref="string" /> with build action names.</returns>
        /// <seealso cref="ExtendedFileNodeProperties.BuildAction" />
        /// <seealso cref="GetAvailableBuildActions" />
        protected internal virtual string[] GetDefaultBuildActions()
        {
            return EmptyBuildActions;
        }
    }
}
